package cn.com.zte.ngbc.dao.mybatis;

import cn.com.zte.ngbc.base.expression.enhance.CompilerContext;
import cn.com.zte.ngbc.base.expression.enhance.OgnlCompiler;
import cn.com.zte.ngbc.base.expression.enhance.OgnlType;
import cn.com.zte.ngbc.base.expression.enhance.VarType;
import cn.com.zte.ngbc.base.expression.type.OgnlTypeFactory;
import cn.com.zte.ngbc.base.frw.model.util.PropertyUtil;
import cn.com.zte.ngbc.base.frw.model.util.PropertyUtil.MyPropertyDescriptor;
import cn.com.zte.ngbc.base.logging.LogManager;
import cn.com.zte.ngbc.base.logging.SysLog;
import cn.com.zte.ngbc.base.util.ClassUtils;
import cn.com.zte.ngbc.base.util.ConvertUtil;
import cn.com.zte.ngbc.base.util.LangUtil;
import cn.com.zte.ngbc.base.util.StringUtil;
import cn.com.zte.ngbc.dao.CursorHandler;
import cn.com.zte.ngbc.frw.model.dm.internal.DefaultEnum;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.ibatis.executor.result.DefaultResultContext;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMap;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.ResultMapping;

public class JdbcCodeGenerator
{
  private static final SysLog log = LogManager.getSysLog(JdbcCodeGenerator.class);
  private static Map<String, SqlQuery> SqlQueryCache = new ConcurrentHashMap();
  
  public static void removeSqlQuery(String id)
  {
    SqlQueryCache.remove(id);
  }
  
  public static SqlQuery getSqlQuery(String id, MappedStatement ms)
  {
    SqlQuery ret = (SqlQuery)SqlQueryCache.get(id);
    if (ret != null) {
      return ret;
    }
    OgnlCompiler c = new OgnlCompiler();
    String code = generateSqlQueryCode(ms);
    try
    {
      ret = (SqlQuery)c.genOneMethodClass(SqlQuery.class, ms.getId(), code, "query", new Class[] { Connection.class, Object.class, CursorHandler.class, Integer.TYPE, Integer.TYPE });
    }
    catch (Exception e)
    {
      throw LangUtil.wrapThrow(e);
    }
    SqlQueryCache.put(id, ret);
    return ret;
  }
  
  public static String generateSqlQueryCode(MappedStatement ms)
  {
    return generateSqlQueryCode(ms.getBoundSql(new Object()).getSql(), ms.getParameterMap(), (ResultMap)ms.getResultMaps().get(0));
  }
  
  private static String generateSqlQueryCode(String sql, ParameterMap parameterMap, ResultMap resultMap)
  {
    CompilerContext context = new CompilerContext(OgnlTypeFactory.getNull(), OgnlTypeFactory.getNull(), null);
    
    VarType con = new VarType("con", OgnlTypeFactory.get(Connection.class));
    context.print(con).println("$1");
    VarType parameterValues = new VarType("parameterValues", OgnlTypeFactory.get(Object.class));
    context.print(parameterValues).println("$2");
    VarType handler = new VarType("handler", OgnlTypeFactory.get(CursorHandler.class));
    context.print(handler).println("$3");
    context.println("int start = $4");
    context.println("int count = $5");
    
    VarType ps = new VarType("ps", OgnlTypeFactory.get(PreparedStatement.class));
    context.print(ps).print("con.prepareStatement(\"").print(sql.replace('\r', ' ').replace('\n', ' ')).println("\")");
    
    context.print("try {\r\n");
    
//    generateSetParameters(context, parameterMap);
    
    context.print(ResultSet.class).print(" rs = ps.executeQuery();\r\n");
    context.print("if (rs.getType() != ").print(Integer.valueOf(1003)).print("/*TYPE_FORWARD_ONLY*/) {\r\n");
    context.print("    if (start != ").print(Integer.valueOf(0)).print("/*NO_ROW_OFFSET*/) {\r\n");
    context.print("        rs.absolute(start);\r\n");
    context.print("    }\r\n");
    context.print("} else {\r\n");
    context.print("    for (int i = 0; i < start; i++) rs.next();\r\n");
    context.print("}\r\n");
    
    context.print("final ").print(DefaultResultContext.class).print(" context = new ");
    context.print(DefaultResultContext.class).print("();\r\n");
    
    context.print("while (rs.next() && context.getResultCount() < count && !context.isStopped()) {\t\r\n");
    
    generateProcessResultSet(context, resultMap);
    
    context.print("    context.nextResultObject(ret);\r\n");
    context.print("    handler.handle(context.getResultCount(), context.getResultObject());\r\n");
    context.print("}\r\n");
    
    context.print("return context.getResultCount();\r\n");
    
    context.print("} finally {\r\n");
    context.print("    ps.close();\r\n");
    context.print("}\r\n");
    
    return context.getCode();
  }
  
  private static void generateSetParameters(CompilerContext context, ParameterMap parameterMap)
  {
    boolean direct = (ClassUtils.isSimpleClass(parameterMap.getType())) || (parameterMap.getType().isEnum());
    boolean map = Map.class.isAssignableFrom(parameterMap.getType());
    context.print(parameterMap.getType()).print(" bean = (");
    context.print(parameterMap.getType()).println(")parameterValues");
    for (int i = 0; i < parameterMap.getParameterMappings().size(); i++)
    {
      ParameterMapping pm = (ParameterMapping)parameterMap.getParameterMappings().get(i);
      if (pm.getMode() != ParameterMode.OUT)
      {
        JdbcJavaTypeMapping type = JdbcJavaTypeMapping.get(String.class);
        context.print("ps.set").print(type.getJdbcType()).print("(");
        context.print(Integer.valueOf(i + 1)).print(", (").print(type.getJavaType()).print(")");
        context.print(ConvertUtil.class).print(".convert(");
        if (direct)
        {
          context.print("bean");
        }
        else if (map)
        {
          context.print("bean.get(\"");
          context.print(pm.getProperty()).print("\")");
        }
        else
        {
          context.print("bean.get");
          context.print(StringUtil.capitalFirst(pm.getProperty())).print("()");
        }
        context.print(", ").print(type.getJavaType()).println(".class))");
      }
    }
  }
  
  private static void generateProcessResultSet(CompilerContext context, ResultMap resultMap)
  {
    VarType ret = new VarType("ret", OgnlTypeFactory.get(resultMap.getType()));
    boolean simple = (ClassUtils.isSimpleClass(resultMap.getType())) || (resultMap.getType().isEnum());
    boolean map = Map.class == ret.type.getType();
    if (!simple) {
      context.print(ret).print("new ").print(map ? HashMap.class : ret.type.getType()).println("()");
    }
    if (simple)
    {
      JdbcJavaTypeMapping type = JdbcJavaTypeMapping.get(resultMap.getType());
      context.print(ret).print("((").print(type.getJavaType()).print(")");
      context.print(ConvertUtil.class).print(".convert(($w)rs.get");
      context.print(type.getJdbcType()).print("(1), ");
      context.print(type.getJavaType()).println(".class))");
    }
    else if ((map) && (resultMap.getResultMappings().isEmpty()))
    {
      context.print(ResultSetMetaData.class).println(" rsmd = rs.getMetaData()");
      context.print("for (int i = 1; i <= rsmd.getColumnCount(); i++) {\r\n");
      context.println("    ret.put(rsmd.getColumnName(i).toLowerCase(), rs.getObject(i))");
      context.print("}\r\n");
    }
    else if ((!map) && (resultMap.getResultMappings().isEmpty()))
    {
      PropertyUtil.MyPropertyDescriptor[] pds = PropertyUtil.getMyPropertyDescriptors(ret.type.getType());
      context.print(Set.class).print(" columns = ");
      context.print(JdbcCodeGenerator.class).println(".getColumnNames(rs)");
      for (int i = 0; i < pds.length; i++)
      {
        PropertyUtil.MyPropertyDescriptor pd = pds[i];
        if (pd.getWriteMethod() != null)
        {
          context.print("if (columns.contains(\"").print(pd.getProperty()).print("\")) ");
          JdbcJavaTypeMapping type = JdbcJavaTypeMapping.get(pd.getPropertyType());
          context.print("ret.set").print(StringUtil.capitalFirst(pd.getProperty()));
          context.print("((").print(type.getJavaType()).print(")");
          context.print(ConvertUtil.class).print(".convert(($w)rs.get");
          context.print(type.getJdbcType()).print("(\"").print(pd.getProperty()).print("\"), ");
          context.print(type.getJavaType()).println(".class))");
        }
      }
    }
    else
    {
      context.print(Set.class).print(" columns = ");
      context.print(JdbcCodeGenerator.class).println(".getColumnNames(rs)");
      for (int i = 0; i < resultMap.getResultMappings().size(); i++)
      {
        ResultMapping rm = (ResultMapping)resultMap.getResultMappings().get(i);
        context.print("if (columns.contains(\"").print(rm.getColumn()).print("\")) ");
        String propertyName = StringUtil.isEmpty(rm.getProperty()) ? rm.getColumn() : rm.getProperty();
        if (map)
        {
          context.print("ret.put(\"").print(propertyName).print("\", ");
          JdbcJavaTypeMapping type = JdbcJavaTypeMapping.get(rm.getJavaType());
          context.print("((").print(type.getJavaType()).print(")");
          context.print(ConvertUtil.class).print(".convert(($w)rs.get");
          context.print(type.getJdbcType()).print("(\"").print(rm.getColumn()).print("\"), ");
          context.print(type.getJavaType()).print(".class))");
          context.println(")");
        }
        else
        {
          context.print("ret.set").print(StringUtil.capitalFirst(propertyName));
          JdbcJavaTypeMapping type = JdbcJavaTypeMapping.get(rm.getJavaType());
          context.print("((").print(type.getJavaType()).print(")");
          context.print(ConvertUtil.class).print(".convert(($w)rs.get");
          context.print(type.getJdbcType()).print("(\"").print(rm.getColumn()).print("\"), ");
          context.print(type.getJavaType()).println(".class))");
        }
      }
    }
  }
  
  public static Set<String> getColumnNames(ResultSet rs)
    throws SQLException
  {
    Set<String> ret = new HashSet();
    ResultSetMetaData rsmd = rs.getMetaData();
    for (int i = 1; i <= rsmd.getColumnCount(); i++)
    {
      ret.add(rsmd.getColumnName(i).toLowerCase());
      ret.add(rsmd.getColumnName(i).toUpperCase());
    }
    return ret;
  }
  
  private static class JdbcJavaTypeMapping
  {
    private final Class<?> javaType;
    private final String jdbcType;
    
    public static JdbcJavaTypeMapping get(Class<?> type)
    {
      if (Integer.TYPE == type) {
        return new JdbcJavaTypeMapping(Integer.class, "Int");
      }
      if (Integer.class == type) {
        return new JdbcJavaTypeMapping(Integer.class, "Int");
      }
      if (Long.TYPE == type) {
        return new JdbcJavaTypeMapping(Long.class, "Long");
      }
      if (Long.class == type) {
        return new JdbcJavaTypeMapping(Long.class, "Long");
      }
      if (Float.TYPE == type) {
        return new JdbcJavaTypeMapping(BigDecimal.class, "BigDecimal");
      }
      if (Float.class == type) {
        return new JdbcJavaTypeMapping(BigDecimal.class, "BigDecimal");
      }
      if (Double.TYPE == type) {
        return new JdbcJavaTypeMapping(BigDecimal.class, "BigDecimal");
      }
      if (Double.class == type) {
        return new JdbcJavaTypeMapping(BigDecimal.class, "BigDecimal");
      }
      if (BigDecimal.class == type) {
        return new JdbcJavaTypeMapping(BigDecimal.class, "BigDecimal");
      }
      if (Date.class.isAssignableFrom(type)) {
        return new JdbcJavaTypeMapping(Date.class, "Date");
      }
      if (DefaultEnum.class.isAssignableFrom(type)) {
        return new JdbcJavaTypeMapping(type, "String");
      }
      if (type.isEnum()) {
        return new JdbcJavaTypeMapping(type, "String");
      }
      return new JdbcJavaTypeMapping(type, "String");
    }
    
    private JdbcJavaTypeMapping(Class<?> javaType, String jdbcType)
    {
      this.javaType = javaType;
      this.jdbcType = jdbcType;
    }
    
    public Class<?> getJavaType()
    {
      return this.javaType;
    }
    
    public String getJdbcType()
    {
      return this.jdbcType;
    }
  }
  
  public static abstract interface SqlQuery
  {
    public abstract int query(Connection paramConnection, Object paramObject, CursorHandler<?> paramCursorHandler, int paramInt1, int paramInt2)
      throws SQLException;
  }
}
